#ifndef BRUSH_H
#define BRUSH_H

//class for reading PPM file
class PPM
{
public:
	PPM() {}
	friend std::istream & operator >> (std::istream & istr, PPM & ppm)
	{
		std::string str ;
		getline(istr, str); // read the first line
		getline(istr, str); // read the second line
		istr>> ppm.width >> ppm.height ; // read the third line
		getline(istr, str) ; // read the third line end
		getline(istr, str) ; // read the forth line

		ppm.data = new int_RGB * [ppm.height];
		for (unsigned i = 0; i < ppm.height; ++i)
		{
			ppm.data[i] = new int_RGB [ppm.width];
			for (unsigned j = 0; j< ppm.width ; ++j)
			{
				
				istr>>(ppm.data[i][j]).r >> ppm.data[i][j].g >> ppm.data[i][j].b; 

			}
		}
		return istr ;
	}
	virtual ~PPM() {this->finalize();}
	inline const int getWidth() const {return this->width ;}
	inline const int getHeight() const {return this->height ;}
	inline const int_RGB & getColor (unsigned x, unsigned y) const
	{
		return data[x][y];
	}
private:

	void finalize ()
	{
		for (unsigned i = 0;i < height; ++i)
		{
			delete []data[i];
		}
		delete []data;
	}
private:
	unsigned width, height ;
	int_RGB * * data ;
};


class Brush
{
public:
	//Brush(): brush_size(0.0005), color() {}
	Brush(): size(0.0025), color(0,0,0),tex_coord(Vector2 (WINDOW_WIDTH /2 ,WINDOW_HEIGHT /2)),
		win_pos(Vector2 (WINDOW_WIDTH /2 ,WINDOW_HEIGHT /2)),pixel_size(50) 
	{ }
	Brush(const Brush & brush)
	{
		this->brushVM = brush.brushVM ;
		this->texVM = brush.texVM ;
		this->objVM = brush.objVM ;
		this->tex_coord = brush.tex_coord ;
		this->win_pos = brush.win_pos ;
		this->size = brush.size ;
		this->color = brush.color ;
		this->pixel_size = brush.pixel_size ;
	}
	virtual ~ Brush() {}
	virtual inline const float get_size() const {return size ;}
	virtual inline void set_size(float size) {this->size = size ;}
	virtual inline const float get_pixel_size() const {return pixel_size ;}
	virtual inline void set_pixel_size(float size) {this->pixel_size = size ;}
	virtual inline void set_win_pos (const Vector2 & pos ) {this->win_pos = pos;}
	virtual inline const Vector2 & get_win_pos () const {return this->win_pos; }
	virtual inline void set_tex_pos (const Vector2 & pos ) {this->tex_coord = pos;}
	virtual inline const Vector2 & get_tex_pos () const {return this->tex_coord; }

	virtual inline void set_color (const int_RGB & c ) {this->color = c;}
	virtual inline const int_RGB & get_color () const { return this->color ;}
	virtual void set_transparent_color (const int_RGB & color ) {}
	virtual bool load_pattern (const char * patternFileName) { return true; }
	virtual void draw ()
	{
		draw(win_pos);
	}
	virtual void tex_draw()
	{
		draw(tex_coord);
	}
	virtual void draw (Vector2 mousePos) 
	{
		update_brush_size();
		getBrushCoordEnv();
		Vector3 objPos = brushVM.mappingWin2Obj(Vector3(mousePos.x, mousePos.y, .99f));
			//mappingWin2Obj(Vector3(mousePos.x, mousePos.y, .99f), bModelViewMatrix, bProjectionMatrix, bViewPort);
		//Set the brush transparent
		
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
		glEnable(GL_BLEND) ;
		glLineWidth(3) ;
		glBegin(GL_LINE_LOOP) ;
		glColor4f(0.75,0.75,0.75,0.75);
		for (float angle = 0.0f ; angle <= 3.1415926f * 2.0f + 0.1f ; angle += 3.1415926f * 2.0f/ 50.0f)
		{
			glVertex3f(objPos.x + cos(angle) * size,  objPos.y + sin(angle) * size, 0.0f);
		}
		glEnd();
		glLineWidth(1) ;
		glDisable(GL_BLEND);
		
		/*
		glBegin(GL_TRIANGLE_FAN) ;
		glColor4f(0.75,0.75,0.75,0.75);
		glVertex3f(objPos.x, objPos.y, 0.0f);
		for (float angle = 0.0f ; angle <= 3.1415926f * 2.0f + 0.1f ; angle += 3.1415926f * 2.0f/ 50.0f)
		{
			glVertex3f(objPos.x + cos(angle) * brush_size,  objPos.y + sin(angle) * brush_size, 0.0f);
		}
		glEnd();
		*/

		//update_brush_pixel_size();
	}

	void getObjCoordEnv ()		{	this->objVM.getCoordEnv();	}
	void getBrushCoordEnv()		{	this->brushVM.getCoordEnv();	}

	//This is check in texcoord;
	bool isInBrush(Vector3 & objCoord)
	{
		float ratio ;
		return isInBrush(objCoord, ratio);
	}
	bool isInBrush(Vector3 & objCoord, float & ratio)
	{
		Vector3 winCoord = texVM.mappingObj2Win(objCoord) ;
			//mappingObj2Win(objCoord, this->modelViewMatrix, this->projectionMatrix, this->viewPort);
		ratio = 0;
		float distance = sqrt((winCoord.x - tex_coord.x) * (winCoord.x - tex_coord.x)
					+ (winCoord.y - tex_coord.y) * (winCoord.y - tex_coord.y));
		if (  distance <= pixel_size )
		{
			ratio = distance / pixel_size; 
			return true ;
		}
		return false ;
	}

	inline bool isVisible(Point point[3])
	{
		Point3 p[3];
		for (int i = 0;i < 3; ++i)
		{
			p[i] = objVM.mappingObj2Win(point[i]); 
		}
		Vector3 v1 = p[1]-p[0];
		Vector3 v2 = p[2]-p[0];
		return  (v1^v2).z < 0;
		//return (v3.z > 0);
	}
	inline Vector3 mappingObj2Win (Vector3 & objCoord)
	{
		return objVM.mappingObj2Win(objCoord);
	}

	void pick_ray(Point3 & p1, Point3 & p2)
	{
		objVM.pick_ray(win_pos, p1, p2);
	}	

	void get_brush_rect (int & x, int & y, unsigned & w, unsigned & h)
	{
		x = win_pos.x - pixel_size ;
		y = win_pos.y - pixel_size ;
		w = 2 * pixel_size ;
		h = 2 * pixel_size ;
	}
	virtual void paint (PseudoMesh * uvexpand)
	{
	}
	void get_brush_pixel (int x, int y, unsigned w, unsigned h )
	{

	}
	void restore_brush_pixel(int x, int y, unsigned w, unsigned h )
	{

	}

	void update_brush_pixel_size()
	{
		size_obj2pixel();
	}
	void update_brush_size() 
	{
		size_pixel2obj();
	}
protected:
	virtual void size_obj2pixel ()
	{
		unsigned x = WINDOW_WIDTH / 2;
		unsigned y = WINDOW_HEIGHT / 2;
		Vector3 objPos = brushVM.mappingWin2Obj(Vector3 (x, y, 0.99f));
		objPos.x += this->size ;
		Vector3 winPos = brushVM.mappingObj2Win(objPos); 
		this->pixel_size = winPos.x - x; 
	}

	virtual void size_pixel2obj()
	{
		Vector3 p1 = brushVM.mappingWin2Obj(Vector3(win_pos.x, win_pos.y, 0.99f));
		Vector3 p2 = brushVM.mappingWin2Obj(Vector3(win_pos.x + pixel_size, win_pos.y, 0.99f));
		this->size = p2.x - p1.x ;
	}

protected:

	virtual bool filter_face(Face * face)
	{
		for (unsigned i = 0; i < this->barrierFace.size(); ++i)
		{
			if (face == this->barrierFace[i]) return false;
		}
		return true; 
	}
	virtual bool filter_strong (Face * face)
	{
		bool result = true;
		// The mouse position is at (0,0)
		HalfEdge * he = face->he ;
		Point2 texCoord [3];
		face->Face2TexCoord(texCoord);
		for (int i = 0; i < 3; ++i)
		{
			if ((texCoord[i]^texCoord[(i+1)%3]) < 0 && face->strongEdge[i])
			{
				this->barrier.push_back(he);
				result = false; 
			}
			he = he->next ;
		}
		return result ;
	}
	virtual void build_barrier (PseudoMesh * uvexpand)
	{
		for (unsigned i = 0; i < uvexpand->v_faces.size(); ++i)
		{
			Face * modelFace = uvexpand->v_faces[i] ;
			filter_strong (modelFace);
		}
		for (unsigned i = 0; i < this->barrier.size(); ++i)
		{
			
			HalfEdge * he = barrier[i];
			Vertex::VVIter v_iter (he);
			Vertex::VVIter v_end = v_iter ;

			Vector2 tex1  = he->from_v->tex_coord ;
			Vector2 tex2  = he->next->from_v->tex_coord ;
			
			do 
			{
				Point2 p = (*v_iter)->tex_coord ;
				if ( ((p-tex1) ^ (p-tex2)) > 0) barrierFace.push_back
					(v_iter.get_he()->flip->face);
				++v_iter ;
			} while (v_end != v_iter) ;
			

			HalfEdge * heflip = barrier[i]->flip;
			Vertex::VVIter v_iter2 (heflip);
			Vertex::VVIter v_end2 = v_iter2 ;

			Vector2 tex12  = heflip->from_v->tex_coord ;
			Vector2 tex22  = heflip->next->from_v->tex_coord ;
			
			do 
			{
				Point2 p = (*v_iter2)->tex_coord ;
				if ( ((p-tex12) ^ (p-tex22)) < 0) barrierFace.push_back
					(v_iter2.get_he()->face);
				++v_iter2 ;
			} while (v_end2 != v_iter2) ;

			//From one end ;
			/*
			HalfEdge * he = barrier[i] ;
			Vector2 tex1  = he->from_v->tex_coord ;
			Vector2 tex2  = he->next->from_v->tex_coord ;
			
			HalfEdge * iter = he; 
			HalfEdge * end = iter ;
			
			do 
			{
				Point2 p = iter->flip->from_v->tex_coord ;
				if ( ((p-tex1) ^ (p-tex2)) > 0) barrierFace.push_back(he->face );
				iter = iter->flip->next ;
			} while (iter != end);
			*/
			/*
			//From another end 
			HalfEdge * he = barrier[i]->flip ;
			Vector2 tex1  = he->from_v->tex_coord ;
			Vector2 tex2  = he->next->from_v->tex_coord ;
			
			HalfEdge * iter = he; 
			HalfEdge * end = iter ;
			
			do 
			{
				Point2 p = iter->flip->from_v->tex_coord ;
				if ( ((p-tex1) ^ (p-tex2)) < 0) barrierFace.push_back(he->flip->face );
				iter = iter->flip->next ;
			} while (iter != end);
			*/
		}
	}
	virtual void clear_barrier ()
	{
		this->barrier.clear();
		this->barrierFace.clear();
	}
	std::vector<HalfEdge * > barrier ;
	std::vector<Face * > barrierFace ;
public:

	ViewMatrix objVM ;
	ViewMatrix brushVM;
	ViewMatrix texVM;

protected:
	float size ; //Geometry size
	float pixel_size ; // Brush pixel size on the screen
						// Left and right view have the same pixel_size; 
	Vector2 win_pos;
	Vector2 tex_coord; 

public:
	
	
	int_RGB color ;

public:
};


//This ds is reserved for developing complicated brushs. 
//Solid: solid color brush
//Gaussian: gaussian distribution brush
//Pattern: some figures pattern on the brush

class SolidBrush : public Brush 
{
public:
	SolidBrush () {}
	SolidBrush (SolidBrush & brush): Brush(brush) {}
	virtual void paint (PseudoMesh * uvexpand)
	{
		//Step 1. build the barrier 
		this->build_barrier(uvexpand);
		unsigned count = 0;
		for (unsigned i = 0;i < uvexpand->v_faces.size(); ++i)
		{
			Point2 p[3];
			//uvexpand->Face2TexCoord(uvexpand->v_faces[i], p);
			Face * modelFace = uvexpand->v_faces[i] ;

			// Step 2. filter the uncessary face
			if (false == this->filter_face(modelFace)) continue; 

			modelFace->Face2TexCoord(p);

			for (unsigned j = 0; j < modelFace->samples.size() ; ++j)
			{
				//map j to barycentric coords
				unsigned x, y;
				unsigned res = modelFace->resolution ;
				index2barycentric(res, j, x, y);
				Point3 point = p[0] * (float (x) / res)   + p[1] * (float(y) / res )
					+ p[2] * (1.0f -float(x+y) /res ) ;
				//Barrier filter
				//if (false == barrier_filter(Point2(point.x, point.y))) continue ;
				//Brush shape filter
				if (isInBrush(point))
				{
					//modelFace->samples[j] = get_color() ;
					modelFace->setColor(get_color(), j);
					count ++ ;
				}
			}
			modelFace->blendLayers ();
		}
		// step 3. clear the barrier ;
		this->clear_barrier();
	}
};

class GaussianBrush : public Brush
{
public:
	GaussianBrush () {}

	virtual void paint (PseudoMesh * uvexpand)
	{
		this->build_barrier(uvexpand);
		//unsigned count = 0;
		for (unsigned i = 0;i < uvexpand->v_faces.size(); ++i)
		{
			Point2 p[3];
			//uvexpand->Face2TexCoord(uvexpand->v_faces[i], p);
			Face * modelFace = uvexpand->v_faces[i] ;
			// filter strong edge
			if (false == this->filter_face(modelFace)) continue; 

			modelFace->Face2TexCoord(p);

			for (unsigned j = 0; j < modelFace->samples.size() ; ++j)
			{
				//map j to barycentric coords
				unsigned x, y;
				unsigned res = modelFace->resolution ;
				index2barycentric(res, j, x, y);
				Point3 point = p[0] * (float (x) / res)   + p[1] * (float(y) / res )
					+ p[2] * (1.0f -float(x+y) /res ) ;
				float ratio; 
				if (isInBrush(point, ratio))
				{
					float color_ratio = linear_gaussian_blur(ratio);
					int_RGB canvert_Color = modelFace->getColor(j);

					
					if ((canvert_Color == int_RGB(-1,-1,-1,255)))
					{
						canvert_Color = int_RGB (255,255,255,255);
					}
					int_RGB c = get_color ();
					c.a = color_ratio * 255 ;
					c = alpha_blend(c, canvert_Color);
					
					/*
					int_RGB c = get_color();
					c.a = color_ratio * 255 ;

					if (!(canvert_Color == int_RGB(-1,-1,-1,255)))
					{
						c = alpha_blend(c, canvert_Color);
					}
					*/
					modelFace->setColor(c,j);
				}
			}
			modelFace->blendLayers ();
		}
		this->clear_barrier();
	}
};

class PatternBrush : public Brush
{
public:
	PatternBrush () {}
	virtual void paint (PseudoMesh * uvexpand)
	{
		unsigned count = 0;
		for (unsigned i = 0;i < uvexpand->v_faces.size(); ++i)
		{
			Point2 p[3];
			//uvexpand->Face2TexCoord(uvexpand->v_faces[i], p);
			Face * modelFace = uvexpand->v_faces[i] ;
			// filter strong edge
			//if (false == this->filter_strong_edges(modelFace)) continue; 
			modelFace->Face2TexCoord(p);

			for (unsigned j = 0; j < modelFace->samples.size() ; ++j)
			{
				//map j to barycentric coords
				unsigned x, y;
				unsigned res = modelFace->resolution ;
				index2barycentric(res, j, x, y);
				Point3 point = p[0] * (float (x) / res)   + p[1] * (float(y) / res )
					+ p[2] * (1.0f -float(x+y) /res ) ;
				float ratio; 
				if (isInBrush(point, offset, ratio))
				{
					float color_ratio = linear_gaussian_blur(ratio);
					int_RGB color = get_color ();
					if (color == this->transparent_color) ;
					else
					{
						modelFace->setColor(get_color(), j);
					}
					count ++ ;
				}
			}
			modelFace->blendLayers ();
		}
	}
	bool load_pattern (const char * patternFileName)
	{
		std::ifstream ifstr(patternFileName);
		if (ifstr)
		{
			ifstr >> ppm ;
			return true;
		}
		ifstr.close();
		return false;
	}

	void set_transparent_color (const int_RGB & color )
	{
		this->transparent_color = color ;
	}

protected:
		//The brush is square when applying on pattern brush ;
	bool isInBrush (Vector3 & objCoord, Vector2 & offset, float & ratio)
	{

		Vector3 winCoord = texVM.mappingObj2Win(objCoord) ;
		if (abs(winCoord.x - tex_coord.x )<= pixel_size 
			&& abs (winCoord.y - tex_coord.y) <= pixel_size)
		{
			offset.x = (winCoord.x - tex_coord.x) / pixel_size;
			offset.y = (winCoord.y - tex_coord.y )/ pixel_size;

			float distance = sqrt((winCoord.x - tex_coord.x) * (winCoord.x - tex_coord.x)
					+ (winCoord.y - tex_coord.y) * (winCoord.y - tex_coord.y));
			ratio = distance / pixel_size; 
			return true;
		}
		return false; 
	}
public:
	
	inline const int_RGB & get_color() const
	{
		float x = (offset.x + 1.0f) / 2.0f;
		float y = (offset.y + 1.0f) / 2.0f;
		unsigned x_idx = min (x * ppm.getWidth(), ppm.getWidth());
		unsigned y_idx = min (y * ppm.getHeight(), ppm.getHeight());
		return ppm.getColor(y_idx, x_idx);
	}

private:
	PPM ppm; 
	Vector2 offset ;
	int_RGB transparent_color;
};
#endif